﻿using System;
using System.Net.Sockets;
using System.Net;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;

namespace EasySocket
{
    public class EasySocketTcpServer : IEasySocketTcpServer
    {
        private Socket _listener;
        private bool _isDisposed;
        private int _clientSocketCount;
        private EasySocketOption _socketOption;
        private AcceptContext _acceptContext;
        private IEasySocketHandler _handler;
        private SocketEventArgsManager _argsManager;
        private readonly ConcurrentDictionary<string, EasySocketToken> _clientDic;

        public EasySocketTcpServer()
        {
            _clientDic = new ConcurrentDictionary<string, EasySocketToken>();
        }

        public int ClientCounts => _clientSocketCount;

        public async Task StartAsync(EasySocketOption option)
        {
            Dispose();
            _socketOption = option ?? throw new ArgumentNullException(nameof(option));
            _handler = option.Handler;
            IPEndPoint ipEndPoint;
            _isDisposed = false;
            if (_socketOption.UseIPV6)
            {
                _listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
                if (string.IsNullOrEmpty(_socketOption.IP))
                    ipEndPoint = (new IPEndPoint(IPAddress.IPv6Any, _socketOption.Port));
                else
                    ipEndPoint = (new IPEndPoint(IPAddress.Parse(_socketOption.IP), _socketOption.Port));
            }
            else
            {
                _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                if (string.IsNullOrEmpty(_socketOption.IP))
                    ipEndPoint = (new IPEndPoint(IPAddress.Any, _socketOption.Port));
                else
                    ipEndPoint = (new IPEndPoint(IPAddress.Parse(_socketOption.IP), _socketOption.Port));
            }

            if (_socketOption.ReusePort)
                _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, _socketOption.ReusePort);

            _listener.NoDelay = _socketOption.NoDelay;
            _listener.SendBufferSize = _socketOption.WriteBufferSize;
            _listener.ReceiveBufferSize = _socketOption.ReadBufferSize;
            _listener.Bind(ipEndPoint);
            _listener.Listen(_socketOption.Backlog < 0 ? 0 : _socketOption.Backlog);
            _argsManager = new SocketEventArgsManager(_socketOption.MaxClientCount, _socketOption.ReadBufferSize, IO_Completed);
            await ProcessAcceptAsync(null);
        }

        public async Task SendAsync(string sessionID, byte[] data)
        {
            if (_socketOption == null) throw new Exception("run the StartAsync method first");
            if (string.IsNullOrWhiteSpace(sessionID)) throw new Exception($"[{sessionID}] is empty");
            if (data == null || data.Length == 0) throw new ArgumentNullException(nameof(data));
            if (_clientDic.ContainsKey(sessionID)) throw new Exception($"[{sessionID}]socket disconnected");
            var client = _clientDic[sessionID];
            await client.Lock?.WaitAsync();
            var writeArgs = _argsManager.Rent(SocketEventTypeEnum.Write);
            writeArgs.UserToken = client;
            writeArgs.SetBuffer(data, 0, data.Length);
            try
            {
                if (!client.Socket.SendAsync(writeArgs))
                    ProcessSended(writeArgs);
            }
            catch (Exception ex)
            {
                ReleseLock(client.Lock);
                _argsManager.Return(writeArgs, SocketEventTypeEnum.Write);
                await DisconnectAsync(client?.SessionId, true);
                //await IgnoreErrorHandler(() => _handler?.HandleErrorAsync(client?.SessionId, ex));
                throw ex;
            }
        }

        public void Dispose()
        {
            _argsManager?.Dispose();
            foreach (var item in _clientDic)
            {
                item.Value?.Lock?.Dispose();
                DisconnectAsync(item.Value, true).ConfigureAwait(false).GetAwaiter().GetResult();
            }
            _clientDic.Clear();
            _isDisposed = true;
            _socketOption = null;
            _handler?.Dispose();
            _listener?.Close();
            _listener?.Dispose();
            _listener = null;
        }

        #region private methods
        private async void AccepteArgs_Completed(object sender, SocketAsyncEventArgs acceptArgs)
        {
            if (acceptArgs.LastOperation == SocketAsyncOperation.Accept)
                await ProcessAcceptedAsync(acceptArgs);
        }

        private async Task ProcessAcceptAsync(SocketAsyncEventArgs acceptArgs)
        {
            if (acceptArgs == null)
            {
                acceptArgs = new SocketAsyncEventArgs();
                acceptArgs.Completed += new EventHandler<SocketAsyncEventArgs>(AccepteArgs_Completed);
            }
            else
                acceptArgs.AcceptSocket = null;
            try
            {
                if (!_isDisposed && _listener != null)
                {
                    if (!_listener.AcceptAsync(acceptArgs))
                        await ProcessAcceptedAsync(acceptArgs);
                }
            }
            catch (Exception ex)
            {
                await IgnoreErrorHandler(() => _handler?.HandleErrorAsync("", ex));
            }
        }

        private async Task ProcessAcceptedAsync(SocketAsyncEventArgs acceptArgs)
        { 
            try
            {
                var socket = acceptArgs.AcceptSocket;
                if (socket.Connected == false)
                    return;

                socket.NoDelay = _socketOption.NoDelay;
                socket.ReceiveBufferSize = _socketOption.ReadBufferSize;
                socket.SendBufferSize = _socketOption.WriteBufferSize;
                _acceptContext = null;
                //accepthandler
                if (_handler != null)
                {
                    _acceptContext = new AcceptContext(this, (IPEndPoint)socket.RemoteEndPoint);
                    await _handler?.AcceptAsync(_acceptContext);
                    if (_acceptContext.IsRefused)
                    {
                        await IgnoreErrorHandler(() => _handler?.HandleErrorAsync(_acceptContext.SessionId, new Exception($"The socket connection[{socket.RemoteEndPoint}] was rejected")));
                        return;
                    }
                }

                if (_clientSocketCount >= _socketOption.MaxClientCount)
                {
                    var e = new Exception($"The number[{_clientSocketCount + 1}] of client connections of the socket service exceeded the maximum[{_socketOption.MaxClientCount}]");
                    await IgnoreErrorHandler(() => _handler?.HandleErrorAsync(_acceptContext.SessionId, e));
                    return;
                }

                Interlocked.Increment(ref _clientSocketCount);
                var sessionId = _acceptContext?.SessionId ?? socket.RemoteEndPoint.ToString();
                var socketClient = new EasySocketToken(socket);
                socketClient.SetSessionId(sessionId);
                _clientDic.AddOrUpdate(sessionId, socketClient, (key, old) =>
                {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                    DisconnectAsync(old, false);
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                    return socketClient;
                });
                var readArgs = _argsManager.Rent(SocketEventTypeEnum.Read);
                readArgs.UserToken = socketClient;
                //通知状态
                await IgnoreErrorHandler(() => _handler?.StateChangeAsync(new EasySocketState(sessionId, "", ConnectionStateEnum.Connected)));

                await ProcessReceiveAsync(readArgs);
            }
            catch (Exception ex)
            {
                await IgnoreErrorHandler(() => _handler?.HandleErrorAsync("", ex));
            }
            finally
            {
                await ProcessAcceptAsync(acceptArgs);
            }
        }

        private async void IO_Completed(object sender, SocketAsyncEventArgs args)
        {
            switch (args.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    await ProcessReceivedAsync(args);
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSended(args);
                    break;
                default:
                    var client = (EasySocketToken)args.UserToken;
                    await DisconnectAsync(client, true);
                    break;
            }
        }

        private async Task ProcessReceiveAsync(SocketAsyncEventArgs readArgs)
        {
            var client = (EasySocketToken)readArgs.UserToken;
            try
            {
                if (client != null && client.Socket != null && client.Socket.Connected)
                {
                    if (!client.Socket.ReceiveAsync(readArgs))
                        await ProcessReceivedAsync(readArgs);
                }
                else
                {
                    if (client.Socket != null)
                        await DisconnectAsync(client?.SessionId, true);
                    _argsManager?.Return(readArgs, SocketEventTypeEnum.Read);
                }

            }
            catch (Exception ex)
            {
                await DisconnectAsync(client?.SessionId, true);
                _argsManager?.Return(readArgs, SocketEventTypeEnum.Read);
                await IgnoreErrorHandler(() => _handler?.HandleErrorAsync(client?.SessionId, ex));
            }
        }

        /// <summary>
        /// 处理接收到数据
        /// </summary>
        /// <param name="readArgs"></param>
        async Task ProcessReceivedAsync(SocketAsyncEventArgs readArgs)
        {
            var client = (EasySocketToken)readArgs.UserToken;
            try
            {
                if (client != null && readArgs.SocketError == SocketError.Success && readArgs.BytesTransferred > 0)
                {
                    var buffer = readArgs.Buffer.AsSpan().Slice(readArgs.Offset, readArgs.BytesTransferred).ToArray();
                    var isFirst = client.IsFirstReceive;
                    try
                    {
                        await _handler?.ReceiveDataAsync(client, buffer);
                        //通知状态
                        if (isFirst && !string.IsNullOrWhiteSpace(client.OldSessionId) && client.SessionId != client.OldSessionId)
                            await IgnoreErrorHandler(() => _handler?.StateChangeAsync(new EasySocketState(client.SessionId, client.OldSessionId, ConnectionStateEnum.Connected)));
                    }
                    catch (Exception ex)
                    {
                        await IgnoreErrorHandler(() => _handler?.HandleErrorAsync(client?.SessionId, ex));
                    }

                    if (client.IsFirstReceive)
                        client.SetNotFirstReceive();
                    await ProcessReceiveAsync(readArgs);
                }
                else
                {
                    await DisconnectAsync(client.SessionId, true);
                    _argsManager?.Return(readArgs, SocketEventTypeEnum.Read);
                }
            }
            catch (Exception ex)
            {
                await DisconnectAsync(client.SessionId, true);
                _argsManager?.Return(readArgs, SocketEventTypeEnum.Read);
                await IgnoreErrorHandler(() => _handler?.HandleErrorAsync(client?.SessionId, ex));
            }
        }

        private void ProcessSended(SocketAsyncEventArgs args)
        {
            var client = (EasySocketToken)args.UserToken;
            _argsManager?.Return(args, SocketEventTypeEnum.Write);
            ReleseLock(client?.Lock);
        }

        private async Task DisconnectAsync(string sessionId, bool isNotice)
        {
            if (_clientDic.TryRemove(sessionId, out var client))
            {
                Interlocked.Decrement(ref _clientSocketCount);
                await DisconnectAsync(client, isNotice);
            }
        }

        private async Task DisconnectAsync(EasySocketToken client, bool isNotice)
        {
            try
            {
                client?.Socket?.Shutdown(SocketShutdown.Both);
                client?.Socket?.Close();
                client?.Socket?.Dispose();
            }
            catch { }
            finally
            {
                if (isNotice)
                {
                    await IgnoreErrorHandler(() => _handler?.StateChangeAsync(new EasySocketState(client?.SessionId, "", ConnectionStateEnum.Disconnected)));
                }
            }
        }

        public static async Task IgnoreErrorHandler(Func<Task> func)
        {
            try
            {
                await func?.Invoke();        
            }
            catch { }
        }

        public static void ReleseLock(SemaphoreSlim semaphoreSlim)
        {
            try
            {
                semaphoreSlim?.Release();
            }catch { }
        }
        #endregion
    }
}